Passed
Push — main ( 97f5b1...4566b4 )
by LCS
02:00
created

Dashboard.tsx ➔ Dashboard   D

Complexity

Conditions 9

Size

Total Lines 165
Code Lines 121

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 121
dl 0
loc 165
rs 4.6666
c 0
b 0
f 0
cc 9

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
import React, { useEffect, useState } from 'react';
2
import { fetchOwm, fetchAqicn } from '../api';
3
import ChartComp from './ChartComp';
4
import MapComp from './MapComp';
5
import './Dashboard.css';
6
7
type Pollutant = { dt: number; aqi: number };
8
type Coords = { lat: number; lon: number };
9
10
export default function Dashboard() {
11
  // 1️⃣ Geolocation state
12
  const [coords, setCoords] = useState<Coords | null>(null);
13
  const [error, setError] = useState<string | null>(null);
14
15
  // 2️⃣ API data state
16
  const [owmCurr, setOwmCurr] = useState<any>(null);
17
  const [owmF, setOwmF] = useState<Pollutant[]>([]);
18
  const [owmH, setOwmH] = useState<Pollutant[]>([]);
19
  const [aq, setAq] = useState<any>(null);
20
21
  // 3️⃣ Ask for GPS once on mount
22
  useEffect(() => {
23
    if (!navigator.geolocation) {
24
      setError('Geolocation is not supported by this browser.');
25
      return;
26
    }
27
    navigator.geolocation.getCurrentPosition(
28
      ({ coords: { latitude, longitude } }) => {
29
        setCoords({ lat: latitude, lon: longitude });
30
      },
31
      (err) => {
32
        let msg = '';
33
        switch (err.code) {
34
          case err.PERMISSION_DENIED:
35
            msg = 'Permission denied. Please allow location access in your browser settings.';
36
            break;
37
          case err.TIMEOUT:
38
            msg = 'The request to get your location timed out.'
39
            break;
40
          default:
41
            msg = 'An Unknown Error Occurred.';
42
        }
43
        setError(msg);
44
      }
45
    );
46
  }, []);
47
48
  // 4️⃣ Fetch AQ data when we have coords
49
  useEffect(() => {
50
    if (!coords) return;
51
    const { lat, lon } = coords;
52
    const now = Math.floor(Date.now() / 1000);
53
54
    fetchOwm('air_pollution', lat, lon).then(setOwmCurr);
55
    fetchOwm('forecast', lat, lon).then(r =>
56
      setOwmF(r.list.map((d: any) => ({ dt: d.dt, aqi: d.main.aqi * 50 })))
57
    );
58
    fetchOwm('history', lat, lon, now - 86400, now).then(r =>
59
      setOwmH(r.list.map((d: any) => ({ dt: d.dt, aqi: d.main.aqi * 50 })))
60
    );
61
    fetchAqicn(lat, lon).then(setAq);
62
  }, [coords]);
63
64
  // 5️⃣ Conditional renders
65
if (!coords) {
66
  return (
67
    <div className="Dashboard Error">
68
      <p>{error || 'Waiting for location…'}</p>
69
      <button
70
        onClick={() => {
71
          const input = window.prompt('Enter location as "lat,lon" (e.g. 3.0738,101.5183)');
72
          if (!input) return;
73
          const parts = ImportsNotUsedAsValues.split(',').map(p => p.trim());
74
          if (parts.length === 2 && !isNaN(+parts[0]) && !isNaN(+parts[1])) {
75
            setCoords({ lat: +parts[0], lon: +parts[1] });
76
            setError(null);
77
            return;
78
          }
79
          
80
          // otherwise treat as place name -> geocode
81
          try {
82
            const resp = await fetch(
83
              `https://nominatim.opemstreetmap.org/search?q=${encodeURIComponent(
84
              input
85
              )}&format=json&limit=1`
86
            );
87
            const results = await resp.json();
88
            if (results.length === 0) {
89
              setError('Location not found, Try another name.');
90
            } else {
91
              const { lat, lon } = results[0];
92
              setCoords({ lat: parseFloat(lat), lon: parseFloat(lon) });
93
              setError(null);
94
            }
95
          } catch (e: any) {
96
            console.error(e);
97
            setError('Geocoding failed, Please try again.');
98
          }
99
        }}
100
      >
101
        Enter location manually
102
      </button> 
103
 
104
       if (!owmCurr || !aq) {
105
    return <div className="dashboard">Loading Air Quality…</div>;
106
  }
107
108
  // 6️⃣ Prepare data sources
109
  const sources = [
110
    {
111
      title: 'OpenWeatherMap',
112
      aqi: owmCurr.list[0].main.aqi * 50,
113
      comps: owmCurr.list[0].components,
114
      time: new Date(owmCurr.list[0].dt * 1000).toLocaleString(),
115
    },
116
    {
117
      title: 'AQICN',
118
      aqi: aq.data.aqi,
119
      comps: Object.fromEntries(
120
        Object.entries(aq.data.iaqi).map(([k, v]: any) => [k, v.v])
121
      ),
122
      time: new Date(aq.data.time.iso).toLocaleString(),
123
    },
124
  ];
125
126
  return (
127
    <div className="dashboard">
128
      <h1>AirMerge</h1>
129
130
      <div className="grid">
131
        {sources.map(({ title, aqi, comps, time }) => (
132
          <div
133
            key={title}
134
            className="card"
135
            style={{ borderLeft: `6px solid ${aqColor(aqi)}` }}
136
          >
137
            <h2>{title}</h2>
138
            <p className="aqi">AQI: {aqi}</p>
139
            <p>Time: {time}</p>
140
            <ul className="components">
141
              {Object.entries(comps).map(([pollutant, value]) => (
142
                <li key={pollutant}>
143
                  <span>{pollutant.toUpperCase()}</span>
144
                  <span>{(value as number).toFixed(1)}</span>
145
                </li>
146
              ))}
147
            </ul>
148
          </div>
149
        ))}
150
      </div>
151
152
      <h2>📊 Historical (24h) & Forecast (4d)</h2>
153
      <div className="chart-container">
154
        <ChartComp hist={owmH} fore={owmF} />
155
      </div>
156
157
      <h2>🗺️ Map Overlay</h2>
158
      <div className="map-container">
159
        <MapComp lat={coords.lat} lon={coords.lon} />
160
      </div>
161
    </div>
162
  );
163
}
164
165
function aqColor(aqi: number) {
166
  if (aqi <= 50) return '#009966';
167
  if (aqi <= 100) return '#ffde33';
168
  if (aqi <= 150) return '#ff9933';
169
  if (aqi <= 200) return '#cc0033';
170
  if (aqi <= 300) return '#660099';
171
  return '#7e0023';
172
}
173